#!/usr/bin/env python

import sys
import datetime
import hashlib
import logging
import pickle
import re
import time
import urllib
import wsgiref.handlers
import hashlib

from string import split
from urlparse import urlparse
from google.appengine.api import urlfetch
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.runtime import apiproxy_errors

import transform_content

HTTP_PREFIX = "http://"
HTTPS_PREFIX = "https://"
HTTP_PREFIX2 = "http%3A//"
HTTPS_PREFIX2 = "https%3A//"

IGNORE_HEADERS = frozenset([
  # Ignore hop-by-hop headers
  'connection',
  'keep-alive',
  'proxy-authenticate',
  'proxy-authorization',
  'te',
  'trailers',
  'transfer-encoding',
  'upgrade',
])

class CookieLists(db.Model):
  site = db.TextProperty(required=True)
  cookie = db.TextProperty(required=True)
  last_updated = db.DateTimeProperty(auto_now=True)
  translated_address = db.TextProperty()
  

class BaseHandler(webapp.RequestHandler):
  def get_relative_url(self):
    slash = self.request.url.find("/", len(self.request.scheme + "://"))
    if slash == -1:
      return "/"
    return self.request.url[slash:]


class HomeHandler(BaseHandler):
  def get(self):
    # Handle the input form to redirect the user to a relative url
    form_url = self.request.get("url")
    if form_url:
      inputted_url = urllib.unquote(form_url)
      if inputted_url.startswith(HTTP_PREFIX):
        inputted_url = inputted_url[len(HTTP_PREFIX):]
      elif inputted_url.startswith(HTTPS_PREFIX):
        inputted_url = inputted_url[len(HTTPS_PREFIX):]
      return self.redirect("/" + inputted_url)

    secure_url = None
    if self.request.scheme == "http":
      secure_url = "https://junytang.appspot.com"

    latest_urls = CookieLists.gql("ORDER BY last_updated DESC").fetch(25)

    context = {"secure_url": secure_url,"latest_urls": latest_urls,}
    self.response.out.write(template.render("main.html", context))

class MirrorHandler(BaseHandler):
  def get(self, base_url):
    return self.doproxy("GET", base_url)
  
  def post(self, base_url):
    return self.doproxy("POST", base_url)
    
  def doproxy(self, method, base_url):
    translated_address = re.sub('http%3A//', 'http://', self.get_relative_url()[1:])
    translated_address = re.sub('https%3A//', 'https://', translated_address)

    if translated_address.startswith(HTTP_PREFIX) or translated_address.startswith(HTTPS_PREFIX):
      mirrored_url = translated_address   
    else:  
      mirrored_url = self.request.scheme + "://" + translated_address

    url = urlparse(mirrored_url)
    base_url = url.hostname

    #logging.debug('Referrer = "%s"',self.request.referer)
    #logging.debug('Base_url = "%s", url = "%s", mirrored_url="%s"', base_url, self.request.url, mirrored_url)
    #logging.debug('Request Head = "%s"',self.request.headers)

    adjusted_headers = {}
    for key, value in self.request.headers.iteritems():
      adjusted_key = key.lower()
      adjusted_headers[adjusted_key] = value

    if 'cookie' in adjusted_headers:
      query = db.GqlQuery("SELECT * FROM CookieLists")
      for q in query:
        if base_url == q.site:
          adjusted_headers['cookie'] = q.cookie
          #logging.debug('Load: Site = "%s", cookie="%s"', base_url, adjusted_headers['cookie'])
          break
        
    try:
      response = urlfetch.fetch(mirrored_url, self.request.body, method , adjusted_headers, False, False)
    except (urlfetch.Error, apiproxy_errors.Error):
      logging.exception("Could not fetch URL")
      logging.debug('Referrer = "%s"',self.request.referer)
      logging.debug('Base_url = "%s", url = "%s", mirrored_url="%s"', base_url, self.request.url, mirrored_url)
      return None   

    if 'content-encoding' in response.headers.caseless_keys: content = zlib.decompress(response.content)
    else: content = response.content  

    content = transform_content.TransformContent(base_url, mirrored_url,content)
    
    adjusted_headers = {}
    for key, value in response.headers.iteritems():
      adjusted_key = key.lower()
      if adjusted_key not in IGNORE_HEADERS:
        adjusted_headers[adjusted_key] = value

    if 'location' in adjusted_headers:
      inputted_url = adjusted_headers['location']
      if inputted_url.startswith(HTTP_PREFIX):
        inputted_url = inputted_url[len(HTTP_PREFIX):]
      adjusted_headers['location'] = "/" + inputted_url

    if 'set-cookie' in adjusted_headers:
      reobj = re.compile(r'domain\s*=\s*[0-9z-zA-Z_\.]+',re.S|re.M|re.I)
      adjusted_headers['set-cookie'] = reobj.sub('domain=' + '.appspot.com', adjusted_headers['set-cookie'])
      query = db.GqlQuery("SELECT * FROM CookieLists")
      cookies_str = adjusted_headers['set-cookie']
      for q in query:
        if base_url == q.site:
          cookies_str = self.MergeCookies(q.cookie, adjusted_headers['set-cookie'])
        break;
      m = hashlib.md5()
      m.update(base_url)
      entry_point = CookieLists(key_name="key_"+m.hexdigest(),site=db.Text(base_url),cookie=db.Text(cookies_str),translated_address=db.Text(translated_address))
      try:
        entry_point.put()
      except (db.Error, apiproxy_errors.Error):
        logging.error('Could not insert EntryPoint. Error= "%s"', db.Error())
        logging.debug('Key_name = "%s" Base_url = "%s" url = "%s", mirrored_url = "%s" cookies = "%s"', m.hexdigest(), base_url, self.request.url, mirrored_url, cookies_str)


    #logging.debug('Header = "%s"', adjusted_headers)

    self.response.set_status(response.status_code)
    for key, value in adjusted_headers.iteritems():
      self.response.headers[key] = value

    self.response.out.write(content)

    if 'location' in adjusted_headers:
      self.redirect("/" + inputted_url)

  def CookieStrToList(self,cookie_str):
    cookies = {}
    keys = split(cookie_str,', ')
    bcontinue = False
    for k in keys:
      s = split(k,'=',1)
      if bcontinue == False:
        cookies[s[0]] = s[1].strip()
        oldk = s[0]
      else:
        cookies[oldk] = cookies[oldk] + ', ' + k
          
      if k.find("expires=") or k.find("Expires=") or k.find("EXPIRES=") >= 0:
        bcontinue = True
      else:
        bcontinue = False

    return cookies
    
  def MergeCookies(self,cookie_str1, cookie_str2):
    cookielist1 =  self.CookieStrToList(cookie_str1)
    cookielist2 =  self.CookieStrToList(cookie_str2)

    for k,v in cookielist2.items():
      cookielist1[k] = v
        
    cookie_str = ""
    for k,v in cookielist1.items():
      cookie_str += k + "=" + v + ", "

    return cookie_str[:len(cookie_str) - 2] 

app = webapp.WSGIApplication([
  (r"/", HomeHandler),
  (r"/main", HomeHandler),
  (r"/([^/]+).*", MirrorHandler)
], debug=True)


def main():
  wsgiref.handlers.CGIHandler().run(app)


if __name__ == "__main__":
  main()
